#!/usr/bin/env bash
# See README.md for info on running these tests.

# TODO: check this test on heroku-16
# testBowerAngularResolution() {
#  compile "bower-angular-resolution"
#  assertCaptured "Bower may need a resolution hint for angular"
#  assertCapturedError
#}

testNoVersion() {
  compile "no-version"
  assertCaptured "engines.node (package.json):  unspecified"
  assertCaptured "Resolving node version 10.x"
  assertCaptured "Downloading and installing node 10."
  assertCapturedSuccess
}

testDisableCache() {
  cache=$(mktmpdir)
  env_dir=$(mktmpdir)

  echo "true" > $env_dir/NODE_VERBOSE
  compile "node-modules-cache-1" $cache $env_dir
  assertCaptured "lodash@1.0.0"
  assertEquals "1" "$(ls -1 $cache/node/node_modules | grep -c lodash | tr -d ' ')"
  assertCapturedSuccess

  compile "node-modules-cache-2" $cache $env_dir
  assertCaptured "lodash@1.0.0"
  assertCapturedSuccess

  echo "false" > $env_dir/NODE_MODULES_CACHE
  compile "node-modules-cache-2" $cache $env_dir
  assertCaptured "lodash@1.3.1"
  assertCapturedSuccess
}

testNodeModulesCached() {
  cache=$(mktmpdir)

  compile "caching" $cache
  assertCaptured "- node_modules"
  assertEquals "1" "$(ls -1 $cache/node/node_modules | grep -c express | tr -d ' ')"
  assertCapturedSuccess
}

testSetBuildEnv() {
  cache=$(mktmpdir)
  env_dir=$(mktmpdir)

  compile "print-node-options"
  assertCaptured "NODE_OPTIONS=--max_old_space_size=2560"

  echo "--max_old_space_size=1234" > $env_dir/NODE_OPTIONS
  compile "print-node-options" $cache $env_dir
  assertCaptured "NODE_OPTIONS=--max_old_space_size=1234"
}

testYarn() {
  compile "yarn"
  assertCaptured "installing yarn"
  assertCaptured "Installing node modules (yarn.lock)"
  assertNotCaptured "Installing node modules (package.json"
  assertCapturedSuccess
}

testYarnCacheDirectory() {
  local cache=$(mktmpdir)
  local env_dir=$(mktmpdir)
  local cache_dir=$(mktmpdir)
  echo "${cache_dir}/yarn"> "$env_dir"/YARN_CACHE_FOLDER
  compile "yarn" $cache $env_dir
  # These will be created if yarn is using the directory for its cache
  assertDirectoryExists ${cache_dir}/yarn
  assertDirectoryExists ${cache_dir}/yarn/v3
  assertFileExists ${cache_dir}/yarn/v3/npm-lodash-4.16.4-01ce306b9bad1319f2a5528674f88297aeb70127
  assertCapturedSuccess
}

testNpm5CacheDirectory() {
  local cache=$(mktmpdir)
  local env_dir=$(mktmpdir)
  local cache_dir=$(mktmpdir)
  echo "${cache_dir}/npm"> "$env_dir"/NPM_CONFIG_CACHE
  compile "npm5" $cache $env_dir
  # These will be created if npm is using the directory for its cache
  assertDirectoryExists ${cache_dir}/npm
  assertDirectoryExists ${cache_dir}/npm/_cacache
  assertCapturedSuccess
}

testBuildWithCache() {
  cache=$(mktmpdir)

  compile "stable-node" $cache
  assertNotCaptured "Restoring cache"
  assertEquals "1" "$(ls -1 $cache/node/node_modules | grep -c hashish | tr -d ' ')"
  assertCapturedSuccess

  compile "stable-node" $cache
  assertCaptured "- node_modules"
  assertFileContains "${STACK}" "${cache}/node/signature"
  assertCapturedSuccess

  rm -rf "$cache/node/node_modules"
  compile "stable-node" $cache
  assertCaptured "- node_modules (not cached - skipping)"
  assertCapturedSuccess
}

testCacheWithPrebuild() {
  local cache=$(mktmpdir)
  local env_dir=$(mktmpdir)
  echo 'true' > "$env_dir"/PREBUILD

  compile "cache-prebuild" $cache
  assertCapturedSuccess

  compile "cache-prebuild" $cache $env_dir
  assertCaptured "Cached directories were not restored due to a change in version of node"
  assertCapturedSuccess
}

testYarnSemver() {
  compile "yarn-semver"
  assertCaptured "Resolving yarn version ~0.28"
  assertCaptured "installing yarn (0.28."
  assertCapturedSuccess
}

testOldYarn() {
  compile "yarn-old-deprecated-version"
  assertCaptured "Resolving yarn version ~0.16"
  assertCaptured "installing yarn (0.16."
  assertCaptured "error \`install\` has been replaced with \`add\`"
  assertCaptured "Outdated Yarn version"
  assertCaptured "Your application is specifying a requirement on an old version of Yarn"
  assertCaptured "https://devcenter.heroku.com/articles/nodejs-support#specifying-a-yarn-version"
  assertCapturedError
}

testYarnV1Semver() {
  compile "yarn-v1-semver"
  assertCaptured "Downloading and installing yarn (1."
  assertCaptured "Installed yarn 1."
  assertCapturedSuccess
}

testYarnInvalid() {
  compile "yarn-invalid"
  assertCaptured "Resolving yarn version 0.171"
  assertCaptured "Could not find Yarn version corresponding to version requirement: 0.171"
  assertCaptured "No matching version found for Yarn: 0.171"
  assertCaptured "https://kb.heroku.com/why-is-my-node-js-build-failing-because-of-no-matching-yarn-versions"
  assertCapturedError
}

testYarnSemverInvalid() {
  compile "yarn-invalid-semver"
  assertCaptured "Resolving yarn version 0.17q"
  assertCaptured "Error: Invalid semantic version \"0.17q\""
  assertCaptured "Invalid semver requirement"
  assertCaptured "https://kb.heroku.com/why-is-my-node-js-build-failing-because-of-an-invalid-semver-requirement"
  assertCapturedError
}

testYarnRun() {
  compile "yarn-run"
  assertCaptured "Running heroku-postbuild (yarn)"
  assertCaptured "foobar"
  assertCapturedSuccess
}

testYarnEngine() {
  compile "yarn-engine"
  assertCaptured "installing yarn (1.4.0)"
  assertCapturedSuccess
}

# If they specify a version of yarn inside package.json but
# don't have a yarn.lock file download and make yarn available
# though we will only install using yarn if a yarn.lock exists
testYarnOnlyEngine() {
  compile "yarn-only-engine"
  assertCaptured "installing yarn (1.4.0)"
  assertCapturedSuccess
}

testErrorYarnAndNpmLockfiles() {
  compile "yarn-and-npm-lockfiles"
  assertNotCaptured "Creating runtime environment"
  assertCaptured "Two different lockfiles found: package-lock.json and yarn.lock"
  assertCaptured "Both npm and yarn have created lockfiles"
  assertCaptured "https://kb.heroku.com/why-is-my-node-js-build-failing-because-of-conflicting-lock-files"
  assertCapturedError
}

testErrorYarnAndNpmShrinkwrap() {
  compile "yarn-and-shrinkwrap-lockfiles"
  assertNotCaptured "Creating runtime environment"
  assertCaptured "Two different lockfiles found"
  assertCaptured "Please make sure there is only one of the following files"
  assertCaptured "https://kb.heroku.com/why-is-my-node-js-build-failing-because-of-conflicting-lock-files"
  assertCapturedError
}

testYarnLockfileOutOfDate() {
  compile "yarn-lockfile-out-of-date"
  assertCaptured "Your lockfile needs to be updated"
  assertCaptured "Outdated Yarn lockfile"
  assertCaptured "https://kb.heroku.com/why-is-my-node-js-build-failing-because-of-an-outdated-yarn-lockfile"
  assertCapturedError
}

testDefaultToNpm5() {
  compile "npm-lockfile-no-version"
  assertCaptured "Using default npm version"
  assertCaptured "Installing node modules (package.json + package-lock)"
  assertCapturedSuccess
}

testDefaultToNpm5WithNode6() {
  compile "npm-lockfile-node-6-no-version"
  assertCaptured "Detected package-lock.json"
  assertCaptured "Bootstrapping npm 5"
  assertCaptured "Installing node modules (package.json + package-lock)"
  assertCapturedSuccess
}

testOldNpmWithLockfile() {
  compile "npm-lockfile-old-version"
  assertCaptured "This version of npm"
  assertCaptured "https://devcenter.heroku.com/articles/nodejs-support#specifying-an-npm-version"
  assertCapturedSuccess
}

testWarnUnmetDepNpm() {
  compile "unmet-dep"
  assertCaptured "fail npm install"
  assertCaptured "may cause runtime issues"
  assertCapturedSuccess
  compile "no-version"
  assertNotCaptured "may cause runtime issues"
  assertCapturedSuccess
}

testWarnUnmetDepYarn() {
  compile "unmet-dep-yarn"
  assertCaptured "fail yarn install"
  assertCaptured "may cause runtime issues"
  assertCapturedSuccess
  compile "no-version"
  assertNotCaptured "may cause runtime issues"
  assertCapturedSuccess
}

testWarnEconnreset() {
  compile "econnreset-mock"
  assertCaptured "may be related to npm versions"
  assertCapturedError
  compile "no-version"
  assertNotCaptured "may be related to npm versions"
  assertCapturedSuccess
}

testWarnNoStart() {
  compile "no-start"
  assertCaptured "may not specify any way to start"
  assertCapturedSuccess
  compile "no-version"
  assertNotCaptured "may not specify any way to start"
  assertCapturedSuccess
}

testWarnDevDeps() {
  compile "missing-devdeps-1"
  assertCaptured "A module may be missing"
  assertNotCaptured "This module may be specified"
  assertCapturedError
  compile "failing-build"
  assertNotCaptured "A module may be missing"
  assertNotCaptured "This module may be specified"
  assertCapturedError
}

testEnvBlacklist() {
  local cache=$(mktmpdir)
  local env_dir=$(mktmpdir)
  echo 'tr_TR.UTF-8' > "$env_dir"/LANG
  echo 'safeVar' > "$env_dir"/SAFE
  compile "echo-lang" $cache $env_dir
  assertCaptured "safeVar"
  assertNotCaptured "tr_TR.UTF-8"
  assertCapturedSuccess
}

testPrePostBuildScripts() {
  compile "pre-post-build-scripts"
  assertCaptured "Running heroku-prebuild"
  assertCaptured "echo heroku-prebuild hook message"
  assertCaptured "Running heroku-postbuild"
  assertCaptured "echo heroku-postbuild hook message"
  assertCapturedSuccess

  compile "stable-node"
  assertNotCaptured "Running heroku-prebuild"
  assertNotCaptured "Running heroku-postbuild"
  assertCapturedSuccess
}

testWarningsOnFailure() {
  compile "many-warnings"
  assertCaptured "troubleshooting-node-deploys"
  assertCaptured "node_modules checked into source"
  assertCaptured "has several known issues"
  assertNotCaptured "please submit a ticket"
  assertCapturedError
}

testDotHerokuCollision() {
  compile "dot-heroku-collision"
  assertCaptured "The directory .heroku could not be created"
  assertCaptured ".heroku file is checked into this project"
  assertNotCaptured "please submit a ticket"
  assertCapturedError

  compile "dot-heroku-collision-2"
  assertCaptured "Build succeeded!"
  assertNotCaptured ".heroku file is checked into this project"
  assertCapturedSuccess
}

testDotHerokuNodeCollision() {
  compile "dot-heroku-node-collision"
  assertCaptured "The directory .heroku/node could not be created"
  assertCaptured ".heroku file is checked into this project"
  assertNotCaptured "please submit a ticket"
  assertCapturedError
}

testMultipleRuns() {
  local compileDir=$(mktmpdir)
  local cacheDir=$(mktmpdir)

  cp -a test/fixtures/stable-node/. ${compileDir}
  compileDir "$compileDir" "$cacheDir"
  assertCapturedSuccess
  compileDir "$compileDir" "$cacheDir"
  assertCapturedSuccess
}

testUntrackedDependencies() {
  compile "missing-grunt"
  assertCaptured "Grunt may not be tracked in package.json"
  assertCapturedError
}

testBadJson() {
  compile "bad-json"
  assertCaptured "Build failed"
  assertCaptured "We're sorry this build is failing"
  assertNotCaptured "Installing binaries"
  assertCapturedError 1 "Unable to parse"
}

testBuildWithUserCacheDirectoriesCamel() {
  cache=$(mktmpdir)

  compile "cache-directories-camel" $cache
  assertCaptured "- non/existent (nothing to cache)"
  assertEquals "1" "$(ls -1 $cache/node/server | grep -c node_modules | tr -d ' ')"
  assertEquals "1" "$(ls -1 $cache/node/client | grep -c node_modules | tr -d ' ')"
  assertCapturedSuccess

  compile "cache-directories-camel" $cache
  assertCaptured "Loading 3 from cacheDirectories"
  assertCaptured "- server/node_modules"
  assertCaptured "- client/node_modules"
  assertCaptured "- non/existent (not cached - skipping)"
  assertCapturedSuccess
}

testConcurrency1X() {
  LOG_CONCURRENCY=true MEMORY_AVAILABLE=512 capture "$(pwd)"/profile/WEB_CONCURRENCY.sh
  assertCaptured "Detected 512 MB available memory, 512 MB limit per process (WEB_MEMORY)"
  assertCaptured "Recommending WEB_CONCURRENCY=1"
  assertCapturedSuccess
}

testConcurrency2X() {
  LOG_CONCURRENCY=true MEMORY_AVAILABLE=1024 capture "$(pwd)"/profile/WEB_CONCURRENCY.sh
  assertCaptured "Detected 1024 MB available memory, 512 MB limit per process (WEB_MEMORY)"
  assertCaptured "Recommending WEB_CONCURRENCY=2"
  assertCapturedSuccess
}

testConcurrencyPerformanceM() {
  LOG_CONCURRENCY=true MEMORY_AVAILABLE=2560 capture "$(pwd)"/profile/WEB_CONCURRENCY.sh
  assertCaptured "Detected 2560 MB available memory, 512 MB limit per process (WEB_MEMORY)"
  assertCaptured "Recommending WEB_CONCURRENCY=5"
  assertCapturedSuccess
}

testConcurrencyPerformanceL() {
   LOG_CONCURRENCY=true MEMORY_AVAILABLE=14336 capture "$(pwd)"/profile/WEB_CONCURRENCY.sh
   assertCaptured "Detected 14336 MB available memory, 512 MB limit per process (WEB_MEMORY)"
   assertCaptured "Recommending WEB_CONCURRENCY=28"
   assertCapturedSuccess
}

testConcurrencyCustomLimit() {
  LOG_CONCURRENCY=true MEMORY_AVAILABLE=1024 WEB_MEMORY=256 capture "$(pwd)"/profile/WEB_CONCURRENCY.sh
  assertCaptured "Detected 1024 MB available memory, 256 MB limit per process (WEB_MEMORY)"
  assertCaptured "Recommending WEB_CONCURRENCY=4"
  assertCapturedSuccess
}

# When /sys/fs/cgroup/memory/memory.limit_in_bytes lies and gives a ridiculous value
# This happens on Dokku for example
testConcurrencyTooHigh() {
  LOG_CONCURRENCY=true MEMORY_AVAILABLE=10000000000 capture "$(pwd)"/profile/WEB_CONCURRENCY.sh
  assertCaptured "Could not determine a reasonable value for WEB_CONCURRENCY"
  assertCaptured "Recommending WEB_CONCURRENCY=1"
  assertCapturedSuccess
}

testInvalidNode() {
  compile "invalid-node"
  assertCaptured "Resolving node version 0.11.333"
  assertCaptured "Could not find Node version corresponding to version requirement: 0.11.333"
  assertCaptured "No matching version found for Node: 0.11.333"
  assertCaptured "https://kb.heroku.com/why-is-my-node-js-build-failing-because-of-no-matching-node-versions"
  assertCapturedError
}

testInvalidNodeSemver() {
  compile "invalid-node-semver"
  assertCaptured "Resolving node version stable"
  assertCaptured "Error: Invalid semantic version \"stable\""
  assertCaptured "Invalid semver requirement"
  assertCapturedError
}

testInvalidIo() {
  compile "invalid-io"
  assertCaptured "Resolving iojs version 2.0.99"
  assertCaptured "Could not find Iojs version corresponding to version requirement: 2.0.99"
  assertCapturedError
}

testSignatureInvalidation() {
  cache=$(mktmpdir)
  env_dir=$(mktmpdir)

  compile "node-0.12.6" $cache
  assertCaptured "Downloading and installing node 0.12.6"
  assertCapturedSuccess

  compile "node-0.12.7" $cache
  assertCaptured "Downloading and installing node 0.12.7"
  assertCaptured "Cached directories were not restored due to a change in version of node"
  assertCapturedSuccess
}

testModulesCheckedIn() {
  cache=$(mktmpdir)
  compile "modules-checked-in" $cache
  assertCapturedSuccess

  compile "modules-checked-in" $cache
  assertCaptured "Prebuild detected"
  assertCaptured "Rebuilding any native modules"
  assertCaptured "(preinstall script)"
  assertCaptured "Installing any new modules"
  assertCaptured "(postinstall script)"
  assertCapturedSuccess
}

testDetectWithPackageJson() {
  detect "stable-node"
  assertCaptured "Node.js"
  assertCapturedSuccess
}

testDetectWithoutPackageJson() {
  detect "no-package-json"
  assertCapturedError 1 ""
}

testIoJs() {
  compile "iojs"
  assertCaptured "engines.iojs (package.json):  1.0."
  assertCaptured "Downloading and installing iojs 1.0."
  assertNotCaptured "Downloading and installing npm"
  assertCapturedSuccess
}

testSpecificVersion() {
  compile "specific-version"
  assertCaptured "Resolving node version"
  assertCaptured "Downloading and installing node 0.10.29"
  assertCaptured "Using default npm version: 1.4.14"
  assertCapturedSuccess
}

testStableVersion() {
  compile "stable-node"
  assertCaptured "Downloading and installing node 0.10."
  assertNotCaptured "We're sorry this build is failing"
  assertCapturedSuccess
}

testUnstableVersion() {
  compile "unstable-version"
  assertCaptured "Resolving node version 0.11.x"
  assertCaptured "Downloading and installing node 0.11."
  assertCapturedSuccess
}

testOldNpm() {
  compile "old-npm"
  assertCaptured "This version of npm (1.2.8000) has several known issues - consider upgrading to the latest release"
  assertNotCaptured "integer expression expected"
  assertCapturedError
}

testOldNpm2() {
  compile "failing-build"
  assertCaptured "This version of npm (1.4.28) has several known issues"
}

testNonexistentNpm() {
  compile "nonexistent-npm"
  assertCaptured "Unable to install npm 1.1.65"
  assertCapturedError 1 ""
}

testSameNpm() {
  compile "same-npm"
  assertCaptured "npm 1.4.28 already installed"
  assertCapturedSuccess
}

testNpmVersionRange() {
  compile "npm-version-range"
  assertCaptured "Bootstrapping npm 5.7.x"
  assertCapturedSuccess
}

testNpmVersionSpecific() {
  compile "npm-version-specific"
  assertCaptured "Bootstrapping npm 2.1.11"
  assertNotCaptured "WARNING"
  assertCapturedSuccess
}

testFailingBuild() {
  compile "failing-build"
  assertCaptured "Building dependencies"
  assertCaptured "Build failed"
  assertCaptured "We're sorry this build is failing"
  assertNotCaptured "Checking startup method"
  assertCapturedError 1 ""
}

testTicketOnFailure() {
  compile "invalid-dependency"
  assertCaptured "troubleshooting-node-deploys"
  assertCaptured "please submit a ticket"
  assertNotCaptured "possible problems"
  assertCapturedError
}

testInfoEmpty() {
  compile "info-empty"
  assertCaptured "engines.node (package.json):  unspecified"
  assertCaptured "engines.npm (package.json):   unspecified"
  assertCaptured "Installing node modules (package.json)"
  assertCapturedSuccess
}

testDangerousRangeStar() {
  compile "dangerous-range-star"
  assertCaptured "Dangerous semver range"
  assertCaptured "Resolving node version *"
  assertCaptured "Downloading and installing node"
  assertCapturedError
}

testDangerousRangeGreaterThan() {
  compile "dangerous-range-greater-than"
  assertCaptured "Dangerous semver range"
  assertCaptured "Resolving node version >0.4"
  assertCaptured "Downloading and installing node"
  assertCapturedError
}

testRangeWithSpace() {
  compile "range-with-space"
  assertCaptured "Resolving node version >= 0.8.x"
  assertCaptured "Downloading and installing node"
  assertCapturedSuccess
}

testInvalidDependency() {
  compile "invalid-dependency"
  assertCaptured "npm ERR! 404"
  assertCapturedError 1 ""
}

testBuildWithUserCacheDirectories() {
  cache=$(mktmpdir)

  compile "cache-directories" $cache
  assertCaptured "Saving 2 cacheDirectories"
  assertEquals "1" "$(ls -1 $cache/node | grep -c bower_components | tr -d ' ')"
  assertEquals "1" "$(ls -1 $cache/node | grep -c node_modules | tr -d ' ')"
  assertCapturedSuccess

  compile "cache-directories" $cache
  assertCaptured "Loading 2 from cacheDirectories"
  assertCaptured "- node_modules"
  assertCaptured "- bower_components"
  assertCapturedSuccess
}

testUserConfig() {
  compile "userconfig"
  assertCaptured "www.google.com"
  assertCaptured "registry error"
  assertCapturedError 1 ""
}

testDefaultProcType() {
  release "stable-node"
  assertCaptured "web: npm start"
  assertCapturedSuccess
}

testDynamicProcfile() {
  compile "dynamic-procfile"
  assertFileContains "web: node index.js customArg" "${compile_dir}/Procfile"
  assertCapturedSuccess
}

testEnvVars() {
  env_dir=$(mktmpdir)
  echo "false" > $env_dir/NPM_CONFIG_PRODUCTION
  compile "stable-node" "$(mktmpdir)" $env_dir
  assertCaptured "NPM_CONFIG_PRODUCTION=false"
  assertCapturedSuccess
}

testNonFileEnvVars() {
  export NPM_CONFIG_FOO=bar
  export NPM_CONFIG_PRODUCTION=false
  compile "stable-node"
  assertCaptured "NPM_CONFIG_FOO=bar"
  assertCaptured "NPM_CONFIG_PRODUCTION=false"
  assertCapturedSuccess
  unset NPM_CONFIG_FOO
  unset NPM_CONFIG_PRODUCTION
}

# In the following tests "lodash" is defined as a devDependency
# testing for lodash is equivalent to testing if devDependencies 
# were installed

# Default behavior: install devDependencies then prunes them away
testDevDependenciesInstalled() {
  compile "dependencies"
  assertCaptured "lodash"
  assertCaptured "Pruning devDependencies"
  assertCaptured "removed 1 package"
  assertCapturedSuccess
}

# Default behavior: install devDependencies then prunes them away with Yarn
testDevDependenciesInstalledYarn() {
  compile "dependencies-yarn"
  assertCaptured "lodash"
  assertCaptured "Pruning devDependencies"
  assertCapturedSuccess
}

# When NPM_CONFIG_PRODUCTION = false we should not prune the devDependencies
testDevDepenenciesWithNoPruning() {
  env_dir=$(mktmpdir)
  echo "false" > $env_dir/NPM_CONFIG_PRODUCTION
  compile "dependencies" "$(mktmpdir)" $env_dir
  assertCaptured "lodash"
  assertCaptured "Skipping because NPM_CONFIG_PRODUCTION is 'false'"
  assertCapturedSuccess

  env_dir=$(mktmpdir)
  echo "true" > $env_dir/NPM_CONFIG_PRODUCTION
  compile "dependencies" "$(mktmpdir)" $env_dir
  assertNotCaptured "lodash"
  assertCaptured "Skipping because NPM_CONFIG_PRODUCTION is 'true'"
  assertCapturedSuccess
}

# When NPM_CONFIG_PRODUCTION = false we should not prune the devDependencies with Yarn
testDevDepenenciesWithNoPruningYarn() {
  env_dir=$(mktmpdir)
  echo "false" > $env_dir/YARN_PRODUCTION
  compile "dependencies-yarn" "$(mktmpdir)" $env_dir
  assertCaptured "lodash"
  assertCaptured "Skipping because YARN_PRODUCTION is 'false'"
  assertCapturedSuccess

  env_dir=$(mktmpdir)
  echo "true" > $env_dir/YARN_PRODUCTION
  compile "dependencies-yarn" "$(mktmpdir)" $env_dir
  assertNotCaptured "lodash"
  assertCaptured "Skipping because YARN_PRODUCTION is 'true'"
  assertCapturedSuccess
}

# When NODE_ENV != production we should not prune the devDependencies
testNodeEnvTestDepenencies() {
  env_dir=$(mktmpdir)
  echo "not-production" > $env_dir/NODE_ENV
  compile "dependencies" "$(mktmpdir)" $env_dir
  assertCaptured "lodash"
  assertCaptured "Skipping because NODE_ENV is not 'production'"
  assertCapturedSuccess
}

# When NODE_ENV != production we should not prune the devDependencies with Yarn
testNodeEnvTestDepenenciesYarn() {
  env_dir=$(mktmpdir)
  echo "not-production" > $env_dir/NODE_ENV
  compile "dependencies-yarn" "$(mktmpdir)" $env_dir
  assertCaptured "lodash"
  assertCaptured "Skipping because NODE_ENV is not 'production'"
  assertCapturedSuccess
}

# When NPM_CONFIG_PRODUCTION = true we should not install devDependencies
testOnlyDependenciesInstalled() {
  env_dir=$(mktmpdir)
  echo "true" > $env_dir/NPM_CONFIG_PRODUCTION
  compile "dependencies" "$(mktmpdir)" $env_dir
  assertNotCaptured "lodash"
  assertCapturedSuccess
}

# When NPM_CONFIG_PRODUCTION = true we should not install devDependencies with Yarn
testOnlyDependenciesInstalledYarn() {
  env_dir=$(mktmpdir)
  echo "true" > $env_dir/YARN_PRODUCTION
  compile "dependencies-yarn" "$(mktmpdir)" $env_dir
  assertNotCaptured "lodash"
  assertCapturedSuccess
}

testModulesCheckedInWithDevDependencies() {
  compile "dependencies-modules-checked-in-with-devdependencies" 
  assertCaptured "Rebuilding any native modules"
  assertCaptured "lodash"
  assertCaptured "removed 1 package"
  assertCapturedSuccess
}

testModulesCheckedInWithoutDevDependencies() {
  compile "dependencies-modules-checked-in-without-devdependencies" 
  assertCaptured "Rebuilding any native modules"
  assertCaptured "added 1 package"
  assertCaptured "lodash"
  assertCaptured "removed 1 package"
  assertCapturedSuccess
}

testOptionalDependencies() {
  env_dir=$(mktmpdir)
  compile "optional-dependencies" "$(mktmpdir)" $env_dir
  assertNotCaptured "NPM_CONFIG_OPTIONAL"
  assertCaptured "less"
  assertCaptured "mime"
  assertCaptured "mkdirp"
  assertCaptured "clean-css"
  assertCaptured "request"
  assertCapturedSuccess
}

testNoOptionalDependencies() {
  env_dir=$(mktmpdir)
  echo "false" > $env_dir/NPM_CONFIG_OPTIONAL
  compile "optional-dependencies" "$(mktmpdir)" $env_dir
  assertCaptured "NPM_CONFIG_OPTIONAL=false"
  assertCaptured "less"
  assertNotCaptured "mime"
  assertNotCaptured "mkdirp"
  assertNotCaptured "clean-css"
  assertNotCaptured "request"
  assertCapturedSuccess
}

testNpmrc() {
  compile "dev-dependencies-npmrc"
  assertCaptured "lodash"
  assertCapturedSuccess
}

testShrinkwrap() {
  compile "shrinkwrap"
  assertCaptured "express@4.10.4"
  assertCaptured "lodash@2.4.0"
  assertCaptured "mocha@2.0.1"
  assertCapturedSuccess
}

testProfileExport() {
  compile "stable-node"
  assertCaptured "Creating runtime environment"
  assertFileContains "export PATH=\"\$HOME/.heroku/node/bin:\$HOME/.heroku/yarn/bin:\$PATH:\$HOME/bin:\$HOME/node_modules/.bin\"" "${compile_dir}/.profile.d/nodejs.sh"
  assertFileContains "export NODE_HOME=\"\$HOME/.heroku/node\"" "${compile_dir}/.profile.d/nodejs.sh"
  assertCapturedSuccess
}

testMultiExport() {
  compile "stable-node"
  assertFileContains "export PATH=" "${bp_dir}/export"
  assertFileContains "/.heroku/node/bin" "${bp_dir}/export"
  assertFileContains "/.heroku/yarn/bin" "${bp_dir}/export"
  assertFileContains "/node_modules/.bin" "${bp_dir}/export"
  assertFileContains "export NODE_HOME=" "${bp_dir}/export"
  assertFileContains "/.heroku/node\"" "${bp_dir}/export"
  assertCapturedSuccess
}

testCIEnvVars() {
  compileTest "ci-env-test"
  assertCaptured "NODE_ENV: test"
  assertCapturedSuccess
}

testCIEnvVarsOverride() {
  env_dir=$(mktmpdir)
  echo "banana" > $env_dir/NODE_ENV

  compileTest "ci-env-test" "$(mktmpdir)" $env_dir

  assertCaptured "NODE_ENV: banana"
  assertCapturedSuccess
}

testCIDependencies() {
  compileTest "ci-dependencies"
  assertCaptured "lodash"
  assertCapturedSuccess
}

testCIDependenciesYarn() {
  compileTest "ci-dependencies-yarn"
  assertCaptured "lodash"
  assertCapturedSuccess
}

testNodeEnv() {
  compile "node-env-consistency"
  assertCaptured "heroku-prebuild: production"
  assertCaptured "preinstall: production"
  assertCaptured "postinstall: production"
  assertCaptured "heroku-postbuild: production"
  assertCapturedSuccess
}

testNodeEnvNpmConfigProductionFalse() {
  env_dir=$(mktmpdir)
  echo "false" > $env_dir/NPM_CONFIG_PRODUCTION
  compile "node-env-consistency" "$(mktmpdir)" $env_dir
  assertCaptured "heroku-prebuild: production"
  assertCaptured "preinstall: production"
  assertCaptured "postinstall: production"
  assertCaptured "heroku-postbuild: production"
  assertCapturedSuccess
}

# Avoid this issue
# https://github.com/npm/npm/issues/17781
testNpmPrune53Issue() {
  compile "npm-prune-5-3-issue"
  assertCaptured "npm 5.3.0 installed"
  assertCaptured "Skipping because npm 5.3.0 fails"
  assertCaptured "https://github.com/npm/npm/issues/17781"
  assertCapturedSuccess
}

# Avoid this issue for users with git dependencies
# https://github.com/npm/npm/issues/19356
testNpmPrune56Issue() {
  compile "npm-prune-5-6-issue"
  assertCaptured "npm 5.6.0"
  assertCaptured "Skipping because npm 5.6.0 sometimes fails"
  assertCaptured "https://github.com/npm/npm/issues/19356"
  assertCapturedSuccess
}

testPluginInstallationBuildTime() {
  # The plugin should be installed for Node 8, 9, 10
  compile "node-8"
  assertFileExists "${compile_dir}/.heroku/heroku-nodejs-plugin/heroku-nodejs-plugin.node"

  compile "node-9"
  assertFileExists "${compile_dir}/.heroku/heroku-nodejs-plugin/heroku-nodejs-plugin.node"

  compile "node-10"
  assertFileExists "${compile_dir}/.heroku/heroku-nodejs-plugin/heroku-nodejs-plugin.node"

  # but not for earlier versions
  compile "node-6"
  assertFileDoesNotExist "${compile_dir}/.heroku/heroku-nodejs-plugin/heroku-nodejs-plugin.node"
}

testPluginInstallationRunTime() {
  local env_dir=$(mktmpdir)
  compile "node-8" "$(mktmpdir)" $env_dir

  # by default $NODE_OPTIONS is unmodifed
  executeStartup $env_dir
  assertEquals "" "$NODE_OPTIONS"
  cleanupStartup

  # If $HEROKU_METRICS_URL is defined at run time, the script
  # should add a require statement to $NODE_OPTIONS
  export HEROKU_METRICS_URL=https://localhost:5000
  executeStartup $env_dir
  assertEquals "--require $compile_dir/.heroku/heroku-nodejs-plugin" "$NODE_OPTIONS"
  cleanupStartup

  # unless $HEROKU_SKIP_NODE_PLUGIN is defined
  export HEROKU_METRICS_URL=https://localhost:5000
  export HEROKU_SKIP_NODE_PLUGIN=true
  executeStartup $env_dir
  assertEquals "" "$NODE_OPTIONS"
  cleanupStartup

  # if $NODE_OPTIONS already exists, it will append the require command
  export HEROKU_METRICS_URL=https://localhost:5000
  export NODE_OPTIONS="--max-old-space-size=128"
  executeStartup $env_dir
  assertEquals "--max-old-space-size=128 --require $compile_dir/.heroku/heroku-nodejs-plugin" "$NODE_OPTIONS"
  cleanupStartup

  # and it will leave it unchanged if $HEROKU_SKIP_NODE_PLUGIN is defined
  export HEROKU_METRICS_URL=https://localhost:5000
  export NODE_OPTIONS="--max-old-space-size=128"
  export HEROKU_SKIP_NODE_PLUGIN=true
  executeStartup $env_dir
  assertEquals "--max-old-space-size=128" "$NODE_OPTIONS"
  cleanupStartup
}

testPluginInstallationUnsupportedNodeRunTime() {
  local env_dir=$(mktmpdir)
  compile "node-6" "$(mktmpdir)" $env_dir

  # This can happen if a user opts-in to the feature but is not using a supported node version
  export HEROKU_METRICS_URL=https://localhost:5000
  executeStartup $env_dir
  assertEquals "" "$NODE_OPTIONS"
  cleanupStartup
}

testMemoryMetrics() {
  env_dir=$(mktmpdir)
  local metrics_log=$(mktemp)
  echo "$metrics_log" > $env_dir/BUILDPACK_LOG_FILE

  compile "pre-post-build-scripts" "$(mktmpdir)" $env_dir
  assertFileContains "measure#buildpack.nodejs.exec.heroku-prebuild.time=" $metrics_log
  assertFileContains "measure#buildpack.nodejs.exec.heroku-prebuild.memory=" $metrics_log
  assertFileContains "measure#buildpack.nodejs.exec.npm-install.time=" $metrics_log
  assertFileContains "measure#buildpack.nodejs.exec.npm-install.memory=" $metrics_log
  assertFileContains "measure#buildpack.nodejs.exec.heroku-postbuild.time=" $metrics_log
  assertFileContains "measure#buildpack.nodejs.exec.heroku-postbuild.memory=" $metrics_log

  # erase the metrics log
  echo "" > $metrics_log
  compile "yarn" "$(mktmpdir)" $env_dir
  assertFileContains "measure#buildpack.nodejs.exec.yarn-install.memory=" "$metrics_log"
  assertFileContains "measure#buildpack.nodejs.exec.yarn-install.time=" "$metrics_log"
  # this fixture does not have pre or post-build scripts
  assertFileNotContains "measure#buildpack.nodejs.exec.heroku-prebuild.time=" $metrics_log
  assertFileNotContains "measure#buildpack.nodejs.exec.heroku-prebuild.memory=" $metrics_log
  assertFileNotContains "measure#buildpack.nodejs.exec.heroku-postbuild.time=" $metrics_log
  assertFileNotContains "measure#buildpack.nodejs.exec.heroku-postbuild.memory=" $metrics_log
}

testBinDetectWarnings() {
  detect "slugignore-package-json"
  assertCapturedError "'package.json' listed in '.slugignore' file"
  assertCapturedError "https://devcenter.heroku.com/articles/slug-compiler#ignoring-files-with-slugignore"

  detect "gitignore-package-json"
  assertCapturedError "'package.json' listed in '.gitignore' file"
  assertCapturedError "https://devcenter.heroku.com/articles/gitignore"

  detect "node-project-missing-package-json"
  assertCapturedError "Application not supported by 'heroku/nodejs' buildpack"
  assertCapturedError "https://devcenter.heroku.com/articles/nodejs-support#activation"
  assertCapturedError "index.js"
  assertCapturedError "src/"
}

# Utils

pushd "$(dirname 0)" >/dev/null
popd >/dev/null

source "$(pwd)"/test/utils
source "$(pwd)"/lib/environment.sh

mktmpdir() {
  dir=$(mktemp -t testXXXXX)
  rm -rf $dir
  mkdir $dir
  echo $dir
}

detect() {
  capture "$(pwd)"/bin/detect "$(pwd)"/test/fixtures/$1
}

compile_dir=""

default_process_types_cleanup() {
  file="/tmp/default_process_types"
  if [ -f "$file" ]; then
    rm "$file"
  fi
}

detect() {
  default_process_types_cleanup
  bp_dir=$(mktmpdir)
  compile_dir=$(mktmpdir)
  cp -a "$(pwd)"/* ${bp_dir}
  cp -a ${bp_dir}/test/fixtures/$1/. ${compile_dir}
  capture ${bp_dir}/bin/detect ${compile_dir}
}

compile() {
  default_process_types_cleanup
  bp_dir=$(mktmpdir)
  compile_dir=$(mktmpdir)
  cp -a "$(pwd)"/* ${bp_dir}
  cp -a ${bp_dir}/test/fixtures/$1/. ${compile_dir}
  capture ${bp_dir}/bin/compile ${compile_dir} ${2:-$(mktmpdir)} $3
}

# This is meant to be run after `compile`. `cleanupStartup` must be run
# after this function is called before other tests are executed
executeStartup() {
  local env_dir=$1

  # On Heroku, $HOME is the /app dir, so we need to set it to
  # the compile_dir here
  export HOME=${compile_dir}

  # we need to set any environment variables set via the env_dir and run
  # all of the .profile.d scripts
  export_env_dir $env_dir

  for f in ${compile_dir}/.profile.d/*; do source $f > /dev/null 2> /dev/null ; done
}

cleanupStartup() {
  unset HOME
  unset NODE_ENV
  unset NODE_HOME
  unset NODE_OPTIONS
  unset DYNO
  unset HEROKU_METRICS_URL
  unset HEROKU_SKIP_NODE_PLUGIN
}

compileTest() {
  default_process_types_cleanup

  local bp_dir=$(mktmpdir)
  local compile_dir=$(mktmpdir)
  local cache_dir=${2:-$(mktmpdir)}
  local env_dir=$3

  cp -a "$(pwd)"/* ${bp_dir}
  cp -a ${bp_dir}/test/fixtures/$1/. ${compile_dir}
  capture ${bp_dir}/bin/test-compile ${compile_dir} ${2:-$(mktmpdir)} $3

  # On Heroku, $HOME is the /app dir, so we need to set it to
  # the compile_dir here
  export HOME=${compile_dir}

  # bin/test is not ran during build, rather during runtime, which means
  # we need to set any environment variables set via the env_dir and run
  # all of the .profile.d scripts
  export_env_dir $env_dir
  for f in ${compile_dir}/.profile.d/*; do source $f > /dev/null 2> /dev/null ; done

  capture ${bp_dir}/bin/test ${compile_dir}

  unset HOME
  unset NODE_ENV
  unset NODE_HOME
}

compileDir() {
  default_process_types_cleanup

  local bp_dir=$(mktmpdir)
  local compile_dir=${1:-$(mktmpdir)}
  local cache_dir=${2:-$(mktmpdir)}
  local env_dir=$3

  cp -a "$(pwd)"/* ${bp_dir}
  capture ${bp_dir}/bin/compile ${compile_dir} ${cache_dir} ${env_dir}
}

release() {
  bp_dir=$(mktmpdir)
  cp -a "$(pwd)"/* ${bp_dir}
  capture ${bp_dir}/bin/release ${bp_dir}/test/fixtures/$1
}

assertFile() {
  assertEquals "$1" "$(cat ${compile_dir}/$2)"
}

source "$(pwd)"/test/shunit2
